
// ===============================================================================================================
// -*- C++ -*-
//
// WavefrontObject.cpp - Wavefront OBJ mesh loader.
//
// Copyright (c) 2011 Guilherme R. Lampert
// guilherme.ronaldo.lampert@gmail.com
//
// This code is licenced under the MIT license.
//
// This software is provided "as is" without express or implied
// warranties. You may freely copy and compile this source into
// applications you distribute provided that the copyright text
// above is included in the resulting source code.
//
// ===============================================================================================================

#include <MeshLib.hpp>

// == Local code ==

static std::string GetFilePath(const std::string & fileName)
{
	// Strip file path from name
	std::string::size_type lastSlash = fileName.find_last_of('\\');

	if (lastSlash == std::string::npos)
	{
		lastSlash = fileName.find_last_of('/');
	}

	std::string path = ((lastSlash != std::string::npos) ? fileName.substr(0, lastSlash + 1) : "");

	return (path);
}

static bool ReadObjTriFace(const char * buffer, MeshLib::Triangle * tri)
{
	unsigned int v1, t1, n1, v2, t2, n2, v3, t3, n3;

	if (sscanf(buffer, "f %u/%u/%u %u/%u/%u %u/%u/%u", &v1, &t1, &n1, &v2, &t2, &n2, &v3, &t3, &n3) == 9)
	{
		// Triagle Face. Vertex + Texture + Normal.
		tri->vertexIndex[0]   = (v1 - 1); tri->vertexIndex[1]   = (v2 - 1); tri->vertexIndex[2]   = (v3 - 1);
		tri->normalIndex[0]   = (n1 - 1); tri->normalIndex[1]   = (n2 - 1); tri->normalIndex[2]   = (n3 - 1);
		tri->texCoordIndex[0] = (t1 - 1); tri->texCoordIndex[1] = (t2 - 1); tri->texCoordIndex[2] = (t3 - 1);
		tri->flags = (MeshLib::POLY_HAS_VERTEX | MeshLib::POLY_HAS_NORMALS | MeshLib::POLY_HAS_TEXTURE);
		return (true);
	}
	if (sscanf(buffer, "f %u/%u %u/%u %u/%u", &v1, &t1, &v2, &t2, &v3, &t3) == 6)
	{
		// Triagle Face. Vertex + Texture.
		tri->vertexIndex[0]   = (v1 - 1); tri->vertexIndex[1]   = (v2 - 1); tri->vertexIndex[2]   = (v3 - 1);
		tri->texCoordIndex[0] = (t1 - 1); tri->texCoordIndex[1] = (t2 - 1); tri->texCoordIndex[2] = (t3 - 1);
		tri->flags = (MeshLib::POLY_HAS_VERTEX | MeshLib::POLY_HAS_TEXTURE);
		return (true);
	}
	if (sscanf(buffer, "f %u//%u %u//%u %u//%u", &v1, &n1, &v2, &n2, &v3, &n3) == 6)
	{
		// Triagle Face. Vertex + Normal.
		tri->vertexIndex[0]   = (v1 - 1); tri->vertexIndex[1]   = (v2 - 1); tri->vertexIndex[2]   = (v3 - 1);
		tri->normalIndex[0]   = (n1 - 1); tri->normalIndex[1]   = (n2 - 1); tri->normalIndex[2]   = (n3 - 1);
		tri->flags = (MeshLib::POLY_HAS_VERTEX | MeshLib::POLY_HAS_NORMALS);
		return (true);
	}
	if (sscanf(buffer, "f %u %u %u", &v1, &v2, &v3) == 3)
	{
		// Triagle Face. Vertex Only.
		tri->vertexIndex[0] = (v1 - 1); tri->vertexIndex[1] = (v2 - 1); tri->vertexIndex[2] = (v3 - 1);
		tri->flags = MeshLib::POLY_HAS_VERTEX;
		return (true);
	}

	// No valid triangle was read, discard this one.
	return (false);
}

static bool ReadObjQuadFace(const char * buffer, MeshLib::Quad * quad)
{
	unsigned int v1, t1, n1, v2, t2, n2, v3, t3, n3, v4, t4, n4;

	if (sscanf(buffer, "f %u/%u/%u %u/%u/%u %u/%u/%u %u/%u/%u", &v1, &t1, &n1, &v2, &t2, &n2, &v3, &t3, &n3, &v4, &t4, &n4) == 12)
	{
		// Quad Face. Vertex + Texture + Normal.
		quad->vertexIndex[0]   = (v1 - 1); quad->vertexIndex[1]   = (v2 - 1); quad->vertexIndex[2]   = (v3 - 1); quad->vertexIndex[3]   = (v4 - 1);
		quad->normalIndex[0]   = (n1 - 1); quad->normalIndex[1]   = (n2 - 1); quad->normalIndex[2]   = (n3 - 1); quad->normalIndex[3]   = (n4 - 1);
		quad->texCoordIndex[0] = (t1 - 1); quad->texCoordIndex[1] = (t2 - 1); quad->texCoordIndex[2] = (t3 - 1); quad->texCoordIndex[3] = (t4 - 1);
		quad->flags = (MeshLib::POLY_HAS_VERTEX | MeshLib::POLY_HAS_NORMALS | MeshLib::POLY_HAS_TEXTURE);
		return (true);
	}
	if (sscanf(buffer, "f %u/%u %u/%u %u/%u %u/%u", &v1, &t1, &v2, &t2, &v3, &t3, &v4, &t4) == 8)
	{
		// Quad Face. Vertex + Texture.
		quad->vertexIndex[0]   = (v1 - 1); quad->vertexIndex[1]   = (v2 - 1); quad->vertexIndex[2]   = (v3 - 1); quad->vertexIndex[3]   = (v4 - 1);
		quad->texCoordIndex[0] = (t1 - 1); quad->texCoordIndex[1] = (t2 - 1); quad->texCoordIndex[2] = (t3 - 1); quad->texCoordIndex[3] = (t4 - 1);
		quad->flags = (MeshLib::POLY_HAS_VERTEX | MeshLib::POLY_HAS_TEXTURE);
		return (true);
	}
	if (sscanf(buffer, "f %u//%u %u//%u %u//%u %u//%u", &v1, &n1, &v2, &n2, &v3, &n3, &v4, &n4) == 8)
	{
		// Quad Face. Vertex + Normal.
		quad->vertexIndex[0] = (v1 - 1); quad->vertexIndex[1] = (v2 - 1); quad->vertexIndex[2] = (v3 - 1); quad->vertexIndex[3] = (v4 - 1);
		quad->normalIndex[0] = (n1 - 1); quad->normalIndex[1] = (n2 - 1); quad->normalIndex[2] = (n3 - 1); quad->normalIndex[3] = (n4 - 1);
		quad->flags = (MeshLib::POLY_HAS_VERTEX | MeshLib::POLY_HAS_NORMALS);
		return (true);
	}
	if (sscanf(buffer, "f %u %u %u %u", &v1, &v2, &v3, &v4) == 4)
	{
		// Quad Face. Vertex Only.
		quad->vertexIndex[0] = (v1 - 1); quad->vertexIndex[1] = (v2 - 1); quad->vertexIndex[2] = (v3 - 1); quad->vertexIndex[3] = (v4 - 1);
		quad->flags = MeshLib::POLY_HAS_VERTEX;
		return (true);
	}

	// No valid quad was read, discard this one.
	return (false);
}

static void ReadMaterialLibrary(const char * fileName, MeshLib::Mesh * mesh)
{
	float ns;
	MathLib::Vec3f color;
	GfxLib::Material * material;
	char buffer[1024], bufferAux[1024];

	FILE * file = fopen(fileName, "rt");

	if (!file)
	{
		CommLib::DbgPrintf(PRINT_ERROR, "Failed to read the object material library: \"%s\"", fileName);
		return;
	}

	while (fgets(buffer, sizeof(buffer), file))
	{
		if (sscanf(buffer, "newmtl %s", bufferAux) > 0)
		{
			MeshLib::Mesh::MaterialMap::const_iterator it = mesh->materials.find(bufferAux);

			if (it == mesh->materials.end())
			{
				material = new GfxLib::Material;
				mesh->materials.insert(MeshLib::Mesh::MaterialMap::value_type(bufferAux, material));
			}
			else
			{
				material = (*it).second;
			}
		}
		else if (sscanf(buffer, "Ns %f", &ns) == 1)
		{
			material->shininess = ns; // Shininess.
		}
		else if (sscanf(buffer, "Kd %f %f %f", &color.x, &color.y, &color.z) == 3)
		{
			material->diffuseColor = color; // Diffuse Color.
		}
		else if (sscanf(buffer, "Ka %f %f %f", &color.x, &color.y, &color.z) == 3)
		{
			material->ambientColor = color; // Ambient Color.
		}
		else if (sscanf(buffer, "Ks %f %f %f", &color.x, &color.y, &color.z) == 3)
		{
			material->specularColor = color; // Specular Color.
		}
		else if (sscanf(buffer, "map_Kd %s", bufferAux) > 0)
		{
			// Diffuse texture map:
			std::string texturePath(GetFilePath(fileName));
			texturePath.append(bufferAux);
			material->diffuseMap = new GfxLib::Texture2D(texturePath.c_str(), GL_CLAMP_TO_EDGE, GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR);
		}
		else
		{
			continue;
		}
	}

	fclose(file);

	CommLib::DbgPrintf(PRINT_MSG, "Loaded material library: \"%s\"", fileName);
}

// ===============================================================================================================

namespace MeshLib {

bool MeshFromWavefrontObject(const char * fileName, Mesh * mesh)
{
	Group * group = 0;
	float x, y, z, u, v;
	char buffer[1024], bufferAux[1024];
	unsigned int nVert = 0, nNorm = 0, nTex = 0;
	int i, cnt;

	Triangle tri;
	Quad quad;

	assert(fileName != 0 && mesh != 0);

	// Open for reading, in TXT mode.
	FILE * file = fopen(fileName, "rt");

	if (!file)
	{
		CommLib::DbgPrintf(PRINT_ERROR, "Failed to open file: \"%s\"", fileName);
		return (false);
	}

	// Count all vertices, tex coords and normals, to allocate exact memory:
	while (fgets(buffer, sizeof(buffer), file))
	{
		switch (buffer[0])
		{
		case 'v':
			switch (buffer[1])
			{
			case ' ':
				++mesh->numVertices;
				break;
			case 'n':
				++mesh->numNormals;
				break;
			case 't':
				++mesh->numTexCoords;
				break;
			default:
				break;
			} // End switch (buffer[1])
			break;
		default:
			break;
		} // End switch (buffer[0])
	}

	// Allocate memory:
	try {

		if (mesh->numVertices > 0)
		{
			mesh->vertices = new MathLib::Vec3f[mesh->numVertices];
		}
		if (mesh->numNormals > 0)
		{
			mesh->normals = new MathLib::Vec3f[mesh->numNormals];
		}
		if (mesh->numTexCoords > 0)
		{
			mesh->texCoords = new MathLib::Vec2f[mesh->numTexCoords];
		}
	} catch (std::bad_alloc) {

		delete[] mesh->vertices;
		delete[] mesh->normals;
		delete[] mesh->texCoords;
		fclose(file);

		CommLib::DbgPrintf(PRINT_ERROR, "Memory allocation failure when parsing file: \"%s\"", fileName);
		return (false);
	}

	rewind(file);

	// Read thru the file, line-by-line...
	while (fgets(buffer, sizeof(buffer), file))
	{
		switch (buffer[0])
		{
		case 'v':
			// Vertex position
			if (sscanf(buffer, "v %f %f %f", &x, &y, &z) == 3)
			{
				mesh->vertices[nVert].x = x;
				mesh->vertices[nVert].y = y;
				mesh->vertices[nVert].z = z;
				++nVert;
			}
			// Normal vector
			else if (sscanf(buffer, "vn %f %f %f", &x, &y, &z) == 3)
			{
				mesh->normals[nNorm].x = x;
				mesh->normals[nNorm].y = y;
				mesh->normals[nNorm].z = z;
				++nNorm;
			}
			// Texture coordinate
			else if (sscanf(buffer, "vt %f %f", &u, &v) == 2)
			{
				mesh->texCoords[nTex].x = u;
				mesh->texCoords[nTex].y = v;
				++nTex;
			}
			break;

		case 'g':
			// OBJ mesh group
			if (sscanf(buffer, "g %s", bufferAux) > 0)
			{
				Mesh::GroupMap::const_iterator it = mesh->polyGroups.find(bufferAux);

				if (it == mesh->polyGroups.end())
				{
					group = new Group;
					mesh->polyGroups.insert(Mesh::GroupMap::value_type(bufferAux, group));
				}
				else
				{
					group = (*it).second;
				}
			}
			break;

		case 'f':
			// Group polygon
			i = 0;
			cnt = 0;

			do {

				i++;
				if (buffer[i] == ' ')
					cnt++;

			} while ((buffer[i] != '\0') && (buffer[i] != '\n'));

			if (cnt == 3) // Triangle
			{
				if (!group)
				{
					group = new Group;
					mesh->polyGroups.insert(Mesh::GroupMap::value_type("defaultGroup", group));
				}

				if (ReadObjTriFace(buffer, &tri)) // If the triangle is valid, store it.
				{
					group->trangles.push_back(tri);
					++mesh->numTriangles;
				}
				else
				{
					CommLib::DbgPrintf(PRINT_WARN, "Found invalid an triangle in OBJ model...");
				}
			}
			else if (cnt == 4) // Quad
			{
				if (!group)
				{
					group = new Group;
					mesh->polyGroups.insert(Mesh::GroupMap::value_type("defaultGroup", group));
				}

				if (ReadObjQuadFace(buffer, &quad)) // If the quad is valid, store it.
				{
					group->quads.push_back(quad);
					++mesh->numQuads;
				}
				else
				{
					CommLib::DbgPrintf(PRINT_WARN, "Found invalid an quad in OBJ model...");
				}
			}
			else // Crazy polygon, unsupported
			{
				CommLib::DbgPrintf(PRINT_ERROR, "Unhandled polygon type found. The OBJ loader only support polygons made of quads or triangles.");
			}
			break;

		default:
			// Check for material library
			if (sscanf(buffer, "usemtl %s", bufferAux) > 0)
			{
				assert(strlen(bufferAux) < MeshLib::materialNameMaxLen);
				strncpy(group->materialName, bufferAux, MeshLib::materialNameMaxLen);
			}
			else if (sscanf(buffer, "mtllib %s", bufferAux) > 0)
			{
				std::string mtlPath(GetFilePath(fileName));
				mtlPath.append(bufferAux);

				// Read the .mtl file:
				ReadMaterialLibrary(mtlPath.c_str(), mesh);
			}
			break;

		} // End switch (buffer[0])
	}

	fclose(file);

	CommLib::DbgPrintf(PRINT_MSG, "Loaded OBJ mesh: \"%s\"", fileName);

	// All good
	return (true);
}

}; // namespace MeshLib {}